// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

`timescale 100 ps / 1 ps
 
module i3c_phy_cmd import i3c_pkg::*; (
    // clock interface
    input  wire                 clk,
    input  wire                 reset_n,

    output wire [  2:0]         error,

    // MGMT signals
    output reg                  busy,
    output status_t             status,
    input  request_t            request,
    input  wire         [  7:0] wr_data,
    output wire         [  7:0] rd_data,
    output wire                 rd_data_valid,

    // Link MGMT signals
    output link_state_t         link_state,
    output link_mode_t          link_mode,
    output reg                  gen_data,
    input  wire                 cap_data,
    input  wire                 cap_data_valid,
    input  device_t             device,

    output transition_t         transition_request,
    input  transition_status_t  transition_status,
    input  wire                 transition_status_valid,

    input  wire                 ack_nack_status,
    input  wire                 ack_nack_status_valid,

    input  wire                 stage_last,
    input  wire                 ibi_detected
);

    typedef enum logic [3:0] {
        STATE_IDLE                = 4'h0,
        STATE_START               = 4'h1,
        STATE_IBI_START           = 4'h2,
        STATE_REPEAT_START        = 4'h3,
        STATE_ADDRESS             = 4'h4,
        STATE_WRITE_DATA          = 4'h5,
        STATE_READ_DATA           = 4'h6,
        STATE_TRANSITION_PARITY   = 4'h7,
        STATE_TRANSITION          = 4'h8,
        STATE_STOP                = 4'h9,
        STATE_GEN_ACK_NACK        = 4'hA,
        STATE_CAP_ACK_NACK        = 4'hB,
        STATE_IBI_ADDRESS         = 4'hC
    } state_t;

    state_t state;

    typedef enum logic [2:0] {
        REQUEST_STATE_IDLE           = 3'b000,
        REQUEST_STATE_CAP_ACK_NACK   = 3'b001,
        REQUEST_STATE_GEN_ACK_NACK   = 3'b010,
        REQUEST_STATE_READ_DATA      = 3'b011,
        REQUEST_STATE_TRANSITION     = 3'b100,
        REQUEST_STATE_IBI_START      = 3'b101,
        REQUEST_STATE_READ_IBI_ADDR  = 3'b110
    } request_state_t;

    request_state_t request_state;

    logic         ccc_init;
    logic         dynamic_address_assignement;
    logic [  2:0] write_data_cntr;
    logic [  2:0] read_data_cntr;
    logic [  2:0] read_data_byte_cntr;
    logic [  7:0] wr_data_temp;
    logic [  7:0] rd_data_temp;
    logic         data_parity;
    logic         transaction_direction;
    logic         read_data_8B_en;
    logic         read_data_valid_en;
    logic         read_data_valid_en_ff;
    logic         read_data_valid_en_r_edge;
    logic         transition_locked;

    // Analyze requests
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            state <= STATE_IDLE;
        end
        else begin
            if (request_state == REQUEST_STATE_CAP_ACK_NACK) begin
                state <= STATE_CAP_ACK_NACK;
            end
            else if (request_state == REQUEST_STATE_GEN_ACK_NACK) begin
                state <= STATE_GEN_ACK_NACK;
            end
            else if (request_state == REQUEST_STATE_READ_DATA) begin
                state <= STATE_READ_DATA;
            end
            else if (request_state == REQUEST_STATE_TRANSITION) begin
                state <= STATE_TRANSITION;
            end
            else if (request_state == REQUEST_STATE_IBI_START) begin
                state <= STATE_IBI_START;
            end
            else if (request_state == REQUEST_STATE_READ_IBI_ADDR) begin
                state <= STATE_IBI_ADDRESS;
            end
            else begin //(request_state == REQUEST_STATE_IDLE)
                if (request == REQUEST_IDLE) begin
                    state <= STATE_IDLE;
                end
                else if (request == REQUEST_START) begin
                    if (status != STATUS_TRANSITION_END_WITH_REPEATED_START) begin
                        state <= STATE_START;
                    end
                end
                else if (request == REQUEST_STOP) begin
                    state <= STATE_STOP;
                end      
                else if (request == REQUEST_ADDRESS) begin
                    state <= STATE_ADDRESS;
                end   
                else if (request == REQUEST_DATA) begin
                    state <= STATE_WRITE_DATA;
                end
                else if (request == REQUEST_TRANSITION_PARITY) begin
                    state <= STATE_TRANSITION_PARITY;
                end
                else if ((request == REQUEST_ACK) | (request == REQUEST_NACK)) begin
                    state <= STATE_GEN_ACK_NACK;
                end
            end
        end
    end

    // Main FSM
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            link_state                  <= LINK_STATE_IDLE;
            link_mode                   <= OPEN_DRAIN;
            request_state               <= REQUEST_STATE_IDLE;
            status                      <= STATUS_IDLE;
            busy                        <= 1'b0;
            ccc_init                    <= 1'b0;
            dynamic_address_assignement <= 1'b0;
            transition_request          <= TRANSITION_END;
            read_data_byte_cntr         <= '0;
            read_data_8B_en             <= 1'b0;
            read_data_valid_en          <= 1'b0;
        end
        else begin 
            case(state)
                STATE_IDLE: begin
                    if (ibi_detected & (status == STATUS_IDLE)) begin
                        request_state  <= REQUEST_STATE_IBI_START;
                    end
                    else begin
                        if (stage_last) begin
                            if (device == DEVICE_I3C) begin
                                link_mode  <= PUSH_PULL;
                            end
                            else begin
                                link_mode  <= OPEN_DRAIN;
                            end

                            link_state <= LINK_STATE_IDLE;
                            status     <= STATUS_IDLE;
                            busy       <= 1'b0;
                        end
                    end
                end

                STATE_START: begin
                    if (stage_last) begin
                        if (busy) begin
                            link_state <= LINK_STATE_REPEAT_START;
                            status     <= STATUS_REPEATED_START;
                        end
                        else begin
                            link_state <= LINK_STATE_START;
                            status     <= STATUS_SINGLE_START;
                        end

                        if (device == DEVICE_I3C) begin
                            link_mode  <= PUSH_PULL;
                        end
                        else begin
                            link_mode  <= OPEN_DRAIN;
                        end

                        busy <= 1'b1;
                        read_data_8B_en <= 1'b0;
                    end
                end

                STATE_IBI_START: begin
                    link_state    <= LINK_STATE_START;
                    link_mode     <= PUSH_PULL;
                    status        <= STATUS_IBI_START;
                    request_state <= REQUEST_STATE_READ_IBI_ADDR;
                    busy          <= 1'b1;
                end

                STATE_ADDRESS: begin
                    // Check CCC addr [0x7E]
                    if (wr_data[7:1] == 7'h7E) begin
                        ccc_init <= 1'b1;
                    end

                    if (stage_last) begin
                        link_state <= LINK_STATE_WRITE;
                        link_mode  <= OPEN_DRAIN;
                        gen_data   <= wr_data_temp[7];

                        if (write_data_cntr == 3'h7) begin
                            status        <= STATUS_ADDRESS_LAST;
                            request_state <= REQUEST_STATE_CAP_ACK_NACK;
                        end
                        else begin
                            status        <= STATUS_ADDRESS;
                        end
                    end
                end

                STATE_IBI_ADDRESS: begin
                    if (stage_last) begin
                        link_state <= LINK_STATE_READ;
                        link_mode  <= OPEN_DRAIN;
                    end
                    else begin
                        if (cap_data_valid) begin
                            if (read_data_cntr == 3'h6) begin
                                status             <= STATUS_IBI_ADDRESS_LAST;
                                read_data_valid_en <= 1'b1;
                            end
                            else if (read_data_cntr == 3'h7) begin
                                request_state      <= REQUEST_STATE_IDLE;
                                read_data_valid_en <= 1'b0;
                            end
                            else begin
                                status             <= STATUS_IBI_ADDRESS;
                                read_data_valid_en <= 1'b0;
                            end
                        end
                    end
                end

                STATE_CAP_ACK_NACK: begin
                    if (stage_last) begin
                        link_state <= LINK_STATE_ACK_NACK;
                        link_mode  <= OPEN_DRAIN;
                    end

                    if (ack_nack_status_valid) begin
                        if (ack_nack_status) begin
                            status        <= STATUS_NACK;
                            request_state <= REQUEST_STATE_IDLE;
                        end
                        else begin
                            status <= STATUS_ACK;

                            if (read_data_8B_en) begin
                                request_state <= REQUEST_STATE_IDLE;
                            end
                            else begin
                                if (transaction_direction) begin
                                    request_state <= REQUEST_STATE_READ_DATA;
                                end
                                else begin
                                    request_state <= REQUEST_STATE_IDLE;
                                end
                            end
                        end
                    end
                end

                STATE_GEN_ACK_NACK: begin
                   if (stage_last) begin
                        link_state <= LINK_STATE_WRITE;
                        link_mode  <= OPEN_DRAIN;
                        
                        if (request == REQUEST_ACK) begin
                            gen_data      <= 1'b0;
                            status        <= STATUS_ACK;
                            request_state <= REQUEST_STATE_READ_DATA;
                        end
                        else if (request == REQUEST_NACK) begin
                            gen_data      <= 1'b1;
                            status        <= STATUS_NACK;
                            request_state <= REQUEST_STATE_IDLE;
                        end
                    end
                end

                STATE_WRITE_DATA: begin
                    if (stage_last) begin
                        if (dynamic_address_assignement) begin
                            link_mode  <= OPEN_DRAIN;
                        end
                        else begin
                            if (device == DEVICE_I3C) begin
                                link_mode  <= PUSH_PULL;
                            end
                            else begin
                                link_mode  <= OPEN_DRAIN;
                            end
                        end

                        link_state <= LINK_STATE_WRITE;

                        gen_data   <= wr_data_temp[7];

                        if (write_data_cntr == 3'h7) begin
                            status <= STATUS_DATA_LAST;

                            if ((device == DEVICE_I2C) | dynamic_address_assignement) begin
                                request_state <= REQUEST_STATE_CAP_ACK_NACK;
                            end
                            else begin
                                request_state <= REQUEST_STATE_IDLE;
                            end
                        end
                        else begin
                            status <= STATUS_DATA;
                        end
                    end
                end

                STATE_READ_DATA: begin
                    if (stage_last) begin
                        link_state <= LINK_STATE_READ; 
                        if (dynamic_address_assignement) begin
                            link_mode  <= OPEN_DRAIN;
                        end
                        else begin
                            if (device == DEVICE_I3C) begin
                                link_mode  <= PUSH_PULL;
                            end
                            else begin
                                link_mode  <= OPEN_DRAIN;
                            end
                        end
                    end
                    else begin
                        if (cap_data_valid) begin
                            if (dynamic_address_assignement) begin
                                read_data_8B_en <= 1'b1;
                                if (read_data_cntr == 3'h7) begin
                                    if (read_data_byte_cntr == 3'h7) begin 
                                        status              <= STATUS_DATA_LAST;
                                        request_state       <= REQUEST_STATE_IDLE;
                                        read_data_byte_cntr <= '0;
                                    end
                                    else begin
                                        read_data_byte_cntr <= read_data_byte_cntr + 3'h1;
                                        status <= STATUS_DATA;
                                    end
                                    read_data_valid_en <= 1'b1;
                                end
                                else begin
                                    status <= STATUS_DATA;
                                    read_data_valid_en <= 1'b0;
                                end
                            end
                            else begin
                                if (read_data_cntr == 3'h7) begin
                                    status <= STATUS_DATA_LAST;

                                    if (device == DEVICE_I2C) begin
                                        request_state <= REQUEST_STATE_GEN_ACK_NACK;
                                    end
                                    else begin
                                        request_state <= REQUEST_STATE_TRANSITION;
                                    end
                                    read_data_valid_en <= 1'b1;
                                end
                                else begin
                                    status <= STATUS_DATA;
                                    read_data_valid_en <= 1'b0;
                                end 
                            end
                        end
                    end
                end

                STATE_TRANSITION_PARITY: begin
                    // Check ENTDAA
                    if ((ccc_cmd_t'(wr_data) == CCC_CMD_ENTDAA) & ccc_init) begin
                        dynamic_address_assignement <= 1'b1;
                    end

                    if (stage_last) begin
                        gen_data   <= data_parity;
                        link_state <= LINK_STATE_WRITE;
                        status     <= STATUS_TRANSITION_PARITY;

                        if (dynamic_address_assignement) begin
                            link_mode  <= OPEN_DRAIN;
                        end
                        else begin
                            link_mode  <= PUSH_PULL;
                        end
                    end
                end

                STATE_TRANSITION: begin
                    if (stage_last) begin
                        link_state <= LINK_STATE_TRANSITION;
                        link_mode  <= PUSH_PULL;
                    end

                    if (~transition_locked) begin                        
                        if ((request == REQUEST_START) | (request == REQUEST_STOP)) begin
                            transition_request <= TRANSITION_END;
                        end
                        else begin
                            transition_request <= TRANSITION_CNT;
                        end
                    end

                    if (transition_status_valid) begin
                        if (transition_status == TRANSITION_STATUS_CNT) begin
                            status        <= STATUS_TRANSITION_CONTINUE;
                            request_state <= REQUEST_STATE_READ_DATA;
                        end
                        else if (transition_status == TRANSITION_STATUS_MASTER_END) begin
                            status        <= STATUS_TRANSITION_END_WITH_REPEATED_START;
                            request_state <= REQUEST_STATE_IDLE;
                        end
                        else if ((transition_status == TRANSITION_STATUS_SLAVE_END) | (transition_status == TRANSITION_STATUS_SLAVE_MASTER_END)) begin
                            status        <= STATUS_TRANSITION_END;
                            request_state <= REQUEST_STATE_IDLE;
                        end
                    end
                end

                STATE_STOP: begin
                    dynamic_address_assignement <= 1'b0;
                    ccc_init                    <= 1'b0;

                    if (stage_last) begin
                        link_state <= LINK_STATE_STOP;
                        status     <= STATUS_STOP;

                        if (device == DEVICE_I3C) begin
                            link_mode  <= PUSH_PULL;
                        end
                        else begin
                            link_mode  <= OPEN_DRAIN;
                        end
                    end
                end

                default: begin
                    request_state <= REQUEST_STATE_IDLE;
                end
            endcase
        end
    end

    // Transition locked signal
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            transition_locked <= 1'b0;   
        end
        else begin 
            if ((state == STATE_TRANSITION) & (request == REQUEST_START)) begin
                transition_locked <= 1'b1;
            end
            else begin
                transition_locked <= 1'b0;
            end
        end
    end

    // Write data counter
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            write_data_cntr <= '0;
        end
        else begin 
            if ((state == STATE_ADDRESS) | (state == STATE_WRITE_DATA)) begin
                if (stage_last) begin
                    if (write_data_cntr == 3'h7) begin
                        write_data_cntr <= '0;
                    end
                    else begin
                        write_data_cntr <= write_data_cntr + 3'h1;
                    end
                end
            end
        end
    end

    // Read data counter
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            read_data_cntr <= '0;
        end
        else begin 
            if ((state == STATE_READ_DATA) | (state == STATE_IBI_ADDRESS)) begin
                if (cap_data_valid) begin
                    if (read_data_cntr == 3'h7) begin
                        read_data_cntr <= '0;
                    end
                    else begin
                        read_data_cntr <= read_data_cntr + 3'h1;
                    end
                end
            end
        end
    end

    // Write data shift register
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            wr_data_temp          <= '0;
            data_parity           <= 1'b0;
            transaction_direction <= 1'b0;
        end
        else begin
            if ( ((request == REQUEST_ADDRESS) & (state != STATE_ADDRESS)) | ((request == REQUEST_DATA) & (state != STATE_WRITE_DATA)) ) begin
                wr_data_temp <= wr_data;
                data_parity <= ^{wr_data, 1'b1};
                if (request == REQUEST_ADDRESS) begin
                    transaction_direction <= wr_data[0];
                end
            end
            else begin
                if (stage_last) begin
                    for (int i=0; i<7; i++) begin
                        wr_data_temp[i+1] <= wr_data_temp[i];
                    end
                end
            end
        end
    end

    // Read data shift registers & capture data
    always_ff @(posedge clk or negedge reset_n) begin
        if (~reset_n) begin
            rd_data_temp <= '0;
        end
        else begin
            if ((state == STATE_READ_DATA) | (state == STATE_IBI_ADDRESS)) begin
                if (stage_last) begin
                    for (int i=0; i<7; i++) begin
                        rd_data_temp[i+1] <= rd_data_temp[i];
                    end
                end
                else begin
                    if (cap_data_valid) begin
                        rd_data_temp[0] <= cap_data;
                    end
                end
            end
        end
    end

    // Read data
    always_ff @(posedge clk) read_data_valid_en_ff <= read_data_valid_en;
    assign read_data_valid_en_r_edge = read_data_valid_en & ~ read_data_valid_en_ff;

    assign rd_data       = rd_data_temp;
    assign rd_data_valid = read_data_valid_en_r_edge;

endmodule
